﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;

namespace Volo.Abp.Account.Web.Pages
{
	public class ConsentModel : AbpPageModel
	{
		[BindProperty(SupportsGet = true)]
		[HiddenInput]
		public string ReturnUrl { get; set; }

		[HiddenInput]
		[BindProperty(SupportsGet = true)]
		public string ReturnUrlHash { get; set; }

		[BindProperty]
		public ConsentModel.ConsentInputModel ConsentInput { get; set; }

		public ConsentModel.ClientInfoModel ClientInfo { get; set; }

		protected IIdentityServerInteractionService Interaction { get; }

		protected IClientStore ClientStore { get; }

		protected IResourceStore ResourceStore { get; }

		public ConsentModel(IIdentityServerInteractionService interaction, IClientStore clientStore, IResourceStore resourceStore)
		{
			this.Interaction = interaction;
			this.ClientStore = clientStore;
			this.ResourceStore = resourceStore;
		}

		public virtual async Task OnGet()
		{
			var request = await this.Interaction.GetAuthorizationContextAsync(this.ReturnUrl);
			if (request == null)
			{
				throw new ApplicationException("No consent request matching request: " + this.ReturnUrl);
			}
			var client = await IClientStoreExtensions.FindEnabledClientByIdAsync(this.ClientStore, request.ClientId);
			if (client == null)
			{
				throw new ApplicationException("Invalid client id: " + request.ClientId);
			}
			var resources = await IResourceStoreExtensions.FindEnabledResourcesByScopeAsync(this.ResourceStore, request.ScopesRequested);
			if (resources == null || (!resources.IdentityResources.Any<IdentityResource>() && !resources.ApiResources.Any<ApiResource>()))
			{
				throw new ApplicationException("No scopes matching: " + request.ScopesRequested.Aggregate((string x, string y) => x + ", " + y));
			}
			this.ClientInfo = new ConsentModel.ClientInfoModel(client);
			var consentInputModel = new ConsentModel.ConsentInputModel();
			consentInputModel.RememberConsent = true;
			consentInputModel.IdentityScopes = resources.IdentityResources.Select(x =>this.CreateScopeViewModel(x, true)).ToList<ConsentModel.ScopeViewModel>();
			consentInputModel.ApiScopes = resources.ApiResources.SelectMany((ApiResource x) => x.Scopes).Select(x=> this.CreateScopeViewModel(x, true)).ToList<ConsentModel.ScopeViewModel>();
			this.ConsentInput = consentInputModel;
			if (resources.OfflineAccess)
			{
				this.ConsentInput.ApiScopes.Add(this.GetOfflineAccessScope(true));
			}
		}

		public virtual async Task<IActionResult> OnPost(string userDecision)
		{
			var processConsentResult = await this.ProcessConsentAsync();
			if (processConsentResult.IsRedirect)
			{
				return base.Redirect(processConsentResult.RedirectUri);
			}
			if (processConsentResult.HasValidationError)
			{
				throw new ApplicationException("Error: " + processConsentResult.ValidationError);
			}
			throw new ApplicationException("Unknown Error!");
		}

		protected virtual async Task<ConsentModel.ProcessConsentResult> ProcessConsentAsync()
		{
			var result = new ConsentModel.ProcessConsentResult();
			ConsentResponse grantedConsent;
			if (this.ConsentInput.UserDecision == "no")
			{
				grantedConsent = ConsentResponse.Denied;
			}
			else
			{
				if (AbpCollectionExtensions.IsNullOrEmpty<ConsentModel.ScopeViewModel>(this.ConsentInput.IdentityScopes) && AbpCollectionExtensions.IsNullOrEmpty<ConsentModel.ScopeViewModel>(this.ConsentInput.ApiScopes))
				{
					throw new UserFriendlyException("You must pick at least one permission");
				}
				grantedConsent = new ConsentResponse
				{
					RememberConsent = this.ConsentInput.RememberConsent,
					ScopesConsented = this.ConsentInput.GetAllowedScopeNames()
				};
			}
			if (grantedConsent != null)
			{
				var authorizationRequest = await this.Interaction.GetAuthorizationContextAsync(this.ReturnUrl);
				if (authorizationRequest == null)
				{
					return result;
				}
				await this.Interaction.GrantConsentAsync(authorizationRequest, grantedConsent, null);
				result.RedirectUri = this.ReturnUrl;
			}
			return result;
		}

		protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
		{
			return new ConsentModel.ScopeViewModel
			{
				Name = identity.Name,
				DisplayName = identity.DisplayName,
				Description = identity.Description,
				Emphasize = identity.Emphasize,
				Required = identity.Required,
				Checked = (check || identity.Required)
			};
		}

		protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
		{
			return new ConsentModel.ScopeViewModel
			{
				Name = scope.Name,
				DisplayName = scope.DisplayName,
				Description = scope.Description,
				Emphasize = scope.Emphasize,
				Required = scope.Required,
				Checked = (check || scope.Required)
			};
		}

		protected virtual ConsentModel.ScopeViewModel GetOfflineAccessScope(bool check)
		{
			return new ConsentModel.ScopeViewModel
			{
				Name = "offline_access",
				DisplayName = "Offline Access",
				Description = "Access to your applications and resources, even when you are offline",
				Emphasize = true,
				Checked = check
			};
		}

		public class ConsentInputModel
		{
			public List<ConsentModel.ScopeViewModel> IdentityScopes { get; set; }

			public List<ConsentModel.ScopeViewModel> ApiScopes { get; set; }

			[Required]
			public string UserDecision { get; set; }

			public bool RememberConsent { get; set; }

			public List<string> GetAllowedScopeNames()
			{
				List<ConsentModel.ScopeViewModel> first = this.IdentityScopes ?? new List<ConsentModel.ScopeViewModel>();
				List<ConsentModel.ScopeViewModel> second = this.ApiScopes ?? new List<ConsentModel.ScopeViewModel>();
				return (from s in first.Union(second)
				where s.Checked
				select s.Name).ToList<string>();
			}
		}

		public class ScopeViewModel
		{
			[Required]
			[HiddenInput]
			public string Name { get; set; }

			public bool Checked { get; set; }

			public string DisplayName { get; set; }

			public string Description { get; set; }

			public bool Emphasize { get; set; }

			public bool Required { get; set; }
		}

		public class ProcessConsentResult
		{
			public bool IsRedirect
			{
				get
				{
					return this.RedirectUri != null;
				}
			}

			public string RedirectUri { get; set; }

			public bool HasValidationError
			{
				get
				{
					return this.ValidationError != null;
				}
			}

			public string ValidationError { get; set; }
		}

		public class ClientInfoModel
		{
			public string ClientName { get; set; }

			public string ClientUrl { get; set; }

			public string ClientLogoUrl { get; set; }

			public bool AllowRememberConsent { get; set; }

			public ClientInfoModel(Client client)
			{
				this.ClientName = client.ClientId;
				this.ClientUrl = client.ClientUri;
				this.ClientLogoUrl = client.LogoUri;
				this.AllowRememberConsent = client.AllowRememberConsent;
			}
		}
	}
}
